Skip to content

feat: add an onFeatureFlags listener you can unsubscribe - #670

Open
turnipdabeets wants to merge 21 commits into
feat/v6-desktop-capture-v1from
feat/v6-on-feature-flags-listener
Open

turnipdabeets wants to merge 21 commits into
feat/v6-desktop-capture-v1from
feat/v6-on-feature-flags-listener

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Brings Flutter to parity with posthog-ios 4.0 (PostHogSDK.shared.onFeatureFlags(_:)) and posthog-android 4.0 (PostHog.onFeatureFlags(callback), PostHog/posthog-android#878). Until now Flutter only had PostHogConfig.onFeatureFlags, which is fixed at setup() and can't be removed.

final subscription = Posthog().onFeatureFlags((loaded) {
  if (loaded.variants['new-checkout'] == 'test') { /* ... */ }
});
subscription.unsubscribe();

Same semantics as iOS and Android:

  • Runs after every load (startup, bootstrap, reloadFeatureFlags(), identify()/group()/reset()), with errorsLoading and the last known flags on failure.
  • If flags already loaded, a new listener gets the current values once, shortly after registering. Skipped if a newer load reaches it first.
  • Can register before setup() and survives close()/setup(). reset() and close() drop the cached load, so a new listener never gets the previous user's flags.

Implementation: a Dart-side registry (FeatureFlagsListeners, @internal). Each platform delivers to it next to the existing config callback: method channel (iOS/Android/macOS), desktop client (Windows/Linux), and posthog-js on web. No native changes, since native already forwards every load.

Web now registers its posthog-js onFeatureFlags listener even without config.onFeatureFlags, and unsubscribes it on close() and before re-registering on repeated setup(). Before, every setup() added another JS listener, so the config callback ran once per setup() call.

A throwing listener is logged and doesn't stop the others. Web skips registration when the page's posthog object has no onFeatureFlags (test stubs), instead of throwing during setup().

Stacked on #664 (needs PostHogFeatureFlagsLoaded).

💚 How did you test it?

  • test/feature_flags_listeners_test.dart: delivery until unsubscribe, one replay, replay skipped when a newer load wins, no replay after clear, unsubscribe during delivery, a throwing listener.
  • One wiring test per platform: method channel (posthog_flutter_io_test), desktop (posthog_flutter_desktop_test), web (posthog_flutter_web_setup_test, also covers repeated setup delivering once).
  • posthog_test: no replay of flags loaded before reset() / close().
  • Mutation-checked: removing the reset clear, the newer-load check, the desktop delivery, or the web re-setup unsubscribe each fails its test.
  • flutter analyze clean, flutter test and flutter test --platform chrome test/posthog_flutter_web_setup_test.dart pass.

🧪 How to test locally (for agents and reviewers)

CI's build-android and build-apple jobs can't pass until posthog-ios and posthog-android 4.0 are released, because no 4.x version resolves yet. To test now, build against the native feat/v4-capture-v1 branches. Don't commit these overrides.

Android: check out feat/v4-capture-v1 of posthog-android, then:

sed -i '' -E 's/^coreVersion=.*/coreVersion=7.99.0/; s/^androidVersion=.*/androidVersion=4.99.0/' gradle.properties
./gradlew :posthog:publishToMavenLocal :posthog-android:publishToMavenLocal -x signMavenPublication

Add mavenLocal() as the first repository in example/android/build.gradle.kts. Use a new version for each republish, because Gradle can reuse a stale one.

iOS/macOS: check out feat/v4-capture-v1 of posthog-ios and point posthog_flutter/darwin/posthog_flutter/Package.swift at it:

.package(name: "posthog-ios", path: "/absolute/path/to/posthog-ios"),

Build: from example/, with Swift Package Manager enabled (flutter config --enable-swift-package-manager), run flutter build apk --debug and flutter build ios --simulator --debug.

Local server: set config.host to http://127.0.0.1:<port>.

  • Android: add android:usesCleartextTraffic="true" to <application> and run adb reverse tcp:<port> tcp:<port>.
  • iOS: set NSAppTransportSecurity > NSAllowsArbitraryLoads in Runner/Info.plist.
  • Server: use HTTP/1.1, and return {"hasFeatureFlags": true} from /array/<token>/config. Otherwise Android skips /flags and reports empty flags.

Run tests on a device with flutter test integration_test/<file>.dart -d <device>.

What to check on this PR: the test and server below cover the 11 listener scenarios. Run python3 server.py server.log, save the test as example/integration_test/flags_listener_e2e_test.dart, and run it. The server listens on port 8793.

Results at 28bc2f0 with posthog-ios 18d94cbb8 and posthog-android db4a11c4: 11/11 on an iOS simulator and 11/11 on an Android 14 emulator. Not yet run on physical devices or against a real PostHog project.

server.py
import gzip, json, sys, urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
state = {"version": 1, "fail": False}
log = open(sys.argv[1], "a", buffering=1)
class H(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def log_message(self, *a): pass
    def send(self, code, obj):
        b = json.dumps(obj).encode()
        self.send_response(code); self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b)
    def do_GET(self):
        u = urllib.parse.urlparse(self.path)
        if u.path == "/__set":
            q = urllib.parse.parse_qs(u.query)
            if "version" in q: state["version"] = int(q["version"][0])
            if "fail" in q: state["fail"] = q["fail"][0] == "1"
            log.write(f"SET {state}\n"); return self.send(200, state)
        log.write(f"GET {self.path}\n"); self.send(200, {"hasFeatureFlags": True})
    def do_POST(self):
        raw = self.rfile.read(int(self.headers.get("Content-Length", 0)))
        if raw[:2] == b"\x1f\x8b": raw = gzip.decompress(raw)
        path = urllib.parse.urlparse(self.path).path
        if path.startswith("/flags"):
            d = json.loads(raw); did = d.get("distinct_id", "?")
            log.write(f"FLAGS did={did} v={state['version']} fail={state['fail']}\n")
            if state["fail"]: return self.send(500, {"error": "forced"})
            kind = did if did.startswith("user-") else "anon"
            val = f"v{state['version']}-{kind}"
            return self.send(200, {"flags": {"e2e-flag": {"key": "e2e-flag", "enabled": True, "variant": val, "reason": {"code": "condition_match", "condition_index": 0}, "metadata": {"id": 1, "version": 1}}},
              "featureFlags": {"e2e-flag": val}, "featureFlagPayloads": {}, "errorsWhileComputingFlags": False, "requestId": "e2e"})
        log.write(f"POST {path} {len(raw)}b\n"); self.send(200, {"status": 1})
ThreadingHTTPServer(("127.0.0.1", 8793), H).serve_forever()
flags_listener_e2e_test.dart
import 'dart:convert';
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:posthog_flutter/posthog_flutter.dart';

const host = 'http://127.0.0.1:8793';
const token = 'phc_local_e2e_test';

Future<void> control(String query) async {
  final c = HttpClient();
  final r = await (await c.getUrl(Uri.parse('$host/__set?$query'))).close();
  await r.drain<void>();
  c.close();
}

PostHogConfig cfg(List<String> order) => PostHogConfig(token)
  ..host = host
  ..flushAt = 1
  ..debug = true
  ..onFeatureFlags = (l) => order.add('config');

String? val(PostHogFeatureFlagsLoaded l) => l.variants['e2e-flag'] as String?;

Future<bool> until(bool Function() cond, [int secs = 20]) async {
  final end = DateTime.now().add(Duration(seconds: secs));
  while (DateTime.now().isBefore(end)) {
    if (cond()) return true;
    await Future<void>.delayed(const Duration(milliseconds: 100));
  }
  return cond();
}

Future<void> settle([int ms = 2500]) =>
    Future<void>.delayed(Duration(milliseconds: ms));

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('onFeatureFlags listener e2e', (tester) async {
    final failures = <String>[];
    void check(String name, bool ok, Object? detail) {
      // ignore: avoid_print
      print('E2E ${ok ? "PASS" : "FAIL"} $name :: $detail');
      if (!ok) failures.add(name);
    }

    final ph = Posthog();
    final order = <String>[];
    await control('version=1&fail=0');

    final a = <PostHogFeatureFlagsLoaded>[];
    final subA = ph.onFeatureFlags((l) {
      a.add(l);
      order.add('listener');
    });
    await ph.setup(cfg(order));
    check('1 listener added before setup gets first load',
        await until(() => a.any((l) => val(l) == 'v1-anon')), a);
    await settle(1000);
    final i = order.indexOf('listener');
    check('2 listener runs before config callback',
        i >= 0 && order.indexOf('config') > i, order);

    final b = <PostHogFeatureFlagsLoaded>[];
    final subB = ph.onFeatureFlags(b.add);
    await settle(1000);
    check('3 late listener gets exactly one replay',
        b.length == 1 && val(b.single) == 'v1-anon', b);

    await control('version=2');
    final reloadA = a.length;
    await ph.reloadFeatureFlags();
    check(
        '4 reloadFeatureFlags reaches all listeners',
        await until(() =>
            a.skip(reloadA).any((l) => val(l) == 'v2-anon') &&
            b.any((l) => val(l) == 'v2-anon')),
        'a=$a b=$b');

    final thrower = ph.onFeatureFlags((_) => throw StateError('boom'));
    final after = <PostHogFeatureFlagsLoaded>[];
    final subAfter = ph.onFeatureFlags(after.add);
    await settle(500);
    after.clear();
    await control('version=3');
    await ph.reloadFeatureFlags();
    check('5 throwing listener does not stop later ones',
        await until(() => after.any((l) => val(l) == 'v3-anon')), after);
    thrower.unsubscribe();
    subAfter.unsubscribe();

    subB.unsubscribe();
    final bCount = b.length;
    await control('version=4');
    await ph.reloadFeatureFlags();
    final got4 = await until(() => a.any((l) => val(l) == 'v4-anon'));
    await settle(1000);
    check('6 unsubscribe stops delivery, others continue',
        got4 && b.length == bCount, 'a4=$got4 b=${b.length}/$bCount');

    await ph.identify(userId: 'user-1');
    check('7 identify delivers the new user flags',
        await until(() => a.any((l) => val(l) == 'v4-user-1')), a.last);

    await ph.reset();
    final c = <PostHogFeatureFlagsLoaded>[];
    final subC = ph.onFeatureFlags(c.add);
    await settle(3000);
    check('8 listener added after reset never sees previous user',
        !c.any((l) => (val(l) ?? '').contains('user-1')), c);
    subC.unsubscribe();

    await control('fail=1');
    final beforeFail = a.length;
    await ph.reloadFeatureFlags();
    final gotFail =
        await until(() => a.skip(beforeFail).any((l) => l.errorsLoading));
    check('9 failed load reports errorsLoading', gotFail,
        a.skip(beforeFail).toList());
    await control('fail=0');

    await ph.close();
    final d = <PostHogFeatureFlagsLoaded>[];
    final subD = ph.onFeatureFlags(d.add);
    await settle(1500);
    check('10 no replay after close', d.isEmpty, d);
    await control('version=5');
    final beforeSetup = a.length;
    await ph.setup(cfg(order));
    check(
        '11 listeners survive close and setup',
        await until(() =>
            a.skip(beforeSetup).any((l) => val(l) == 'v5-anon') &&
            d.any((l) => val(l) == 'v5-anon')),
        'a=${a.skip(beforeSetup).toList()} d=$d');

    subA.unsubscribe();
    subD.unsubscribe();
    // ignore: avoid_print
    print('E2E DONE failures=${jsonEncode(failures)}');
    expect(failures, isEmpty);
  }, timeout: const Timeout(Duration(minutes: 5)));
}

📝 Checklist

  • All commits are signed (see Commit signing)
  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Claude Code wrote this while aligning the flags API across iOS 4, Android 4 and Flutter 6. Kept the listener in Dart instead of adding a native subscribe/unsubscribe channel method: native already pushes every load to Dart, so a Dart registry covers all five platforms with one implementation.

🤖 Generated with Claude Code

@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 25, 2026 16:45
@turnipdabeets turnipdabeets self-assigned this Oct 25, 2026

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed 34d79f4: 2 Should fix.

See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.

Comment thread posthog_flutter/lib/src/posthog.dart Resolved
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

posthog-flutter Compliance Report

Date: 2026-11-02T23:53:31.489315+00:00
Duration: 97614ms

✅ All Tests Passed!

47/47 tests passed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 144ms
Format Validation.Event Has Uuid ✅ 120ms
Format Validation.Event Has Lib Properties ✅ 117ms
Format Validation.Distinct Id Is String ✅ 115ms
Format Validation.Token Is Present ✅ 114ms
Format Validation.Custom Properties Preserved ✅ 117ms
Format Validation.Event Has Timestamp ✅ 117ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 116ms
Retry Behavior.Retries On 503 ✅ 5328ms
Retry Behavior.Does Not Retry On 400 ✅ 2117ms
Retry Behavior.Does Not Retry On 401 ✅ 2117ms
Retry Behavior.Respects Retry After Header ✅ 8125ms
Retry Behavior.Implements Backoff ✅ 15444ms
Retry Behavior.Retries On 500 ✅ 5226ms
Retry Behavior.Retries On 502 ✅ 5226ms
Retry Behavior.Retries On 504 ✅ 5224ms
Retry Behavior.Max Retries Respected ✅ 15436ms
Deduplication.Generates Unique Uuids ✅ 128ms
Deduplication.Preserves Uuid On Retry ✅ 5227ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10333ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5231ms
Deduplication.No Duplicate Events In Batch ✅ 125ms
Deduplication.Different Events Have Different Uuids ✅ 118ms
Compression.Sends Gzip When Enabled ✅ 117ms
Batch Format.Uses Proper Batch Structure ✅ 115ms
Batch Format.Flush With No Events Sends Nothing ✅ 109ms
Batch Format.Multiple Events Batched Together ✅ 126ms
Error Handling.Does Not Retry On 403 ✅ 2117ms
Error Handling.Does Not Retry On 413 ✅ 2117ms
Error Handling.Retries On 408 ✅ 5225ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 14ms
Request Payload.Flags Request Uses V2 Query Param ✅ 10ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 9ms
Request Payload.Flags Request Omits Authorization Header ✅ 9ms
Request Payload.Token In Flags Body Matches Init ✅ 9ms
Request Payload.Groups Round Trip ✅ 11ms
Request Payload.Groups Default To Empty Object ✅ 10ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 9ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 10ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 9ms
Request Lifecycle.No Flags Request On Init Alone ✅ 4ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 113ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 16ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 10ms
Retry Behavior.Retries Flags On 502 ✅ 115ms
Retry Behavior.Retries Flags On 504 ✅ 114ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 117ms

@turnipdabeets
turnipdabeets force-pushed the feat/v6-on-feature-flags-listener branch from 5a85f1b to 2f7955e Compare October 26, 2026 16:28
@turnipdabeets
turnipdabeets force-pushed the chore/v6-flags-loaded-result branch from de3a190 to 0a7ab2a Compare October 26, 2026 18:38
@turnipdabeets
turnipdabeets force-pushed the feat/v6-on-feature-flags-listener branch from 2f7955e to a194366 Compare October 26, 2026 18:39

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subscription API and ordinary delivery/replay paths look sound. Two dispatch-boundary cases remain: a config callback can restore an invalidated snapshot after reset/close, and a synchronous config-callback exception prevents runtime subscribers from receiving the load. Both reproduce through the public Dart API and IO channel harness.

AI-assisted review.

@dustinbyrne
dustinbyrne requested a review from a team October 26, 2026 20:22

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed d4105d9: 3 Should fix.

Base automatically changed from chore/v6-flags-loaded-result to chore/v6-adopt-posthog-android-4 October 28, 2026 14:55

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous web-loader, stale-native-notification and synchronous-replay findings are resolved in the focused regressions. Two runtime subscription routes remain incomplete: native auto-init does not activate the bridge when subscription is the first Dart operation, and Android manual reloads update neither subscribers nor their replay snapshot. Both reproduce independently; native 4.0 build availability remains a separate prerequisite.

AI-assisted review.

Comment thread posthog_flutter/lib/src/posthog.dart Resolved
@dustinbyrne
dustinbyrne requested a review from a team October 28, 2026 19:27
Base automatically changed from chore/v6-adopt-posthog-android-4 to chore/v6-adopt-posthog-ios-4 October 29, 2026 16:01
turnipdabeets and others added 5 commits October 29, 2026 15:07
- Require posthog-ios 4.x.
- Adapt to the reloadFeatureFlags(_:) callback that now passes a
  PostHogFeatureFlagsLoaded (PostHog/posthog-ios#913).
- Note the payload decoding changes from PostHog/posthog-ios#903.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
PostHog/posthog-ios#905 removes the legacy version/build properties from
lifecycle events in 4.0, so Flutter iOS and macOS apps stop sending them.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…listener

posthog-ios 4.0 removes the didReceiveFeatureFlags notification.
* chore!: adopt posthog-android 4.0

* chore!: pass the loaded flags to onFeatureFlags and reloadFeatureFlags (#664)

* chore!: pass the loaded flags to onFeatureFlags and reloadFeatureFlags

onFeatureFlags and reloadFeatureFlags now carry a PostHogFeatureFlagsLoaded with the enabled flags, their values and whether loading failed, matching posthog-ios 4.0 and posthog-android 4.0. On web, reloadFeatureFlags completes right away with the flags loaded so far, because posthog-js doesn't report when a reload finishes.

* fix: complete the web reloadFeatureFlags with the reloaded flags

Wait for the next posthog-js onFeatureFlags call instead of returning the flags already loaded, so a failed reload reports errorsLoading. Complete right away when flags are disabled, and after twice the flags request timeout when posthog-js skips the request without calling back.

* chore: use PostHogFeatureFlagsCallback from posthog-android 4.0

* chore: fix ktlint function-signature

* fix: await posthog-js reloadFeatureFlagsAsync on web

The next onFeatureFlags callback can carry a response from a request already in flight, so reloadFeatureFlags() could complete with flags from before the reload. posthog-js 1.438.2+ settles reloadFeatureFlagsAsync() after this reload's own request; use it when present and keep the callback path for older posthog-js.

* docs: pass the loaded flags in the setup example

* docs: drop best-effort web note from the person and group property methods

* fix(web): wait for posthog-js flags retries before reporting a reload failure

posthog-js retries a timed-out /flags request once by default, 500 ms later, inside the same reload. The older-JS deadline of twice the request timeout reported cached flags with errorsLoading set before a successful retry landed.

* docs: note where onFeatureFlags and errorsLoading differ on Android and web

* docs: describe the older posthog-js reload timeout as the full retry budget

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
@turnipdabeets
turnipdabeets force-pushed the chore/v6-adopt-posthog-ios-4 branch from fd88c7e to 7eebbca Compare October 29, 2026 19:10
turnipdabeets and others added 7 commits October 29, 2026 15:10
… callback

A config callback that threw kept listeners from getting the load, and a
reset() or close() it started was undone when the load was cached after it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…oader

A registration whose unsubscribe isn't stored yet (close() during the synchronous replay) or never arrives (the loader snippet's stub) now goes inert once setup() or close() runs again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dart bumps a generation when reset() or close() clears the listener registry and sends it with setup, reset and close. Android and iOS tag each onFeatureFlags notification with the generation they had when it fired, and Dart drops ones from an earlier generation, so the previous user's flags can't repopulate the replay cache.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
With native auto-init, Posthog().onFeatureFlags() can be the app's only
Dart SDK call, and nothing created the platform that installs the channel
handler, so native loads never reached the listener.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…teners

posthog-android passes a manual reload only to its completion, so no
onFeatureFlagsCallback arrives and listeners missed the load while new
ones replayed the older snapshot. A reload that finishes after a reset or
close is still dropped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@turnipdabeets
turnipdabeets force-pushed the feat/v6-on-feature-flags-listener branch from 26f2755 to 4d81d9e Compare October 29, 2026 19:12
posthog-android 4.0 drops RRStyle and the wireframe tree fields from the
internal RRWireframe; screenshot frames don't need a style.

Co-authored-by: Claude Opus 5.5 <[email protected]>

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-assisted review. The earlier findings are resolved. One web reset/replay regression remains when reset waits behind async beforeSend; the inline comment includes the reproduction and controls.

@dustinbyrne
dustinbyrne requested a review from a team October 30, 2026 19:08
Base automatically changed from chore/v6-adopt-posthog-ios-4 to feat/v6-desktop-capture-v1 October 30, 2026 22:34
… phase3/670

# Conflicts:
#	api/posthog_flutter.api.json
#	posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt
#	posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift
#	posthog_flutter/lib/posthog_flutter_web.dart
#	posthog_flutter/lib/src/feature_flags_loaded.dart
#	posthog_flutter/lib/src/posthog.dart
#	posthog_flutter/lib/src/posthog_flutter_desktop.dart
#	posthog_flutter/lib/src/posthog_flutter_io.dart
#	posthog_flutter/lib/src/posthog_flutter_web_handler.dart
#	posthog_flutter/test/posthog_flutter_desktop_test.dart
#	posthog_flutter/test/posthog_flutter_io_test.dart
Posthog() creates PosthogFlutterIO, whose constructor set the channel handler
and aborted without a binding. Retry the install instead, and fail native calls
with a PlatformException so each caller's existing fallback applies.
@marandaneto
marandaneto marked this pull request as draft November 2, 2026 19:21
@marandaneto

Copy link
Copy Markdown
Member

cannot be tested e2e so moving to draft

@turnipdabeets
turnipdabeets marked this pull request as ready for review November 2, 2026 20:48
@turnipdabeets
turnipdabeets marked this pull request as draft November 3, 2026 02:29
@turnipdabeets
turnipdabeets marked this pull request as ready for review November 3, 2026 02:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants